/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// r_light.c

#include "quakedef.h"

float d_lightstylevalue[MAX_LIGHTSTYLES];	// 8.8 fraction of base light value

// one texture array for each lightstyle value
ID3D10ShaderResourceView *d3d_LightmapSRVs[MAX_SURFACESTYLES];
static unsigned *d3d_LightmapData[MAX_SURFACESTYLES][MAX_LIGHTMAPS];
static BOOL ActiveStyles[MAX_SURFACESTYLES];
static int lm_allocated[LIGHTMAP_SIZE];
int current_lightmap_texture = 0;


ID3D10Buffer *d3d_LightConstants = NULL;

class CLightHandler : public CD3DHandler
{
public:
	void OnCreateDevice (void)
	{
		// create our constant buffer for lightstyles
		D3D10_BUFFER_DESC cbDesc = {
			sizeof (d_lightstylevalue),
			D3D10_USAGE_DYNAMIC,
			D3D10_BIND_CONSTANT_BUFFER,
			D3D10_CPU_ACCESS_WRITE,
			0
		};

		d3d_Device->CreateBuffer (&cbDesc, NULL, &d3d_LightConstants);
	}

	void OnReleaseDevice (void)
	{
		SAFE_RELEASE (d3d_LightConstants);
		SAFE_RELEASE (d3d_LightmapSRVs[0]);
		SAFE_RELEASE (d3d_LightmapSRVs[1]);
		SAFE_RELEASE (d3d_LightmapSRVs[2]);
		SAFE_RELEASE (d3d_LightmapSRVs[3]);
	}

	void OnModeChange (void)
	{
		// re-initialize the lightstyles their default values
		for (int i = 0; i < MAX_LIGHTSTYLES; i++)
			d_lightstylevalue[i] = ('m' - 'a') / 6.0f;

		// and write them to our shader regs
		float *c = NULL;

		if (SUCCEEDED (d3d_LightConstants->Map (D3D10_MAP_WRITE_DISCARD, 0, (void **) &c)))
		{
			memcpy (c, d_lightstylevalue, sizeof (d_lightstylevalue));
			d3d_LightConstants->Unmap ();
		}

		// rebind the lightstyles cbuffer
		d3d_Device->VSSetConstantBuffers (3, 1, &d3d_LightConstants);

		// bind the lightmap textures from slots 10 onwards so that they won't be overwritten and need only be bound once per map
		d3d_Device->PSSetShaderResources (TEXTURE_SLOT_LIGHTMAP0, 4, d3d_LightmapSRVs);
	}
} d3d_LightHandler;


/*
=============================================================================

LIGHTMAPS

=============================================================================
*/

void R_ActivateLightStyle (int style, int lm)
{
	// alloc lightdata for this lightmap if required - using full alloc so it will memset 0 and clear the map
	// this needs to go into the mapPool as scratch was used as a holding space for building surface verts
	if (!d3d_LightmapData[style][lm])
		d3d_LightmapData[style][lm] = (unsigned *) mapPool->Alloc (LIGHTMAP_SIZE * LIGHTMAP_SIZE * 4);

	// and mark it as active
	ActiveStyles[style] = TRUE;
}


BOOL LM_AllocBlock (int w, int h, int *x, int *y)
{
	int	i, j;
	int	best, best2;

	best = LIGHTMAP_SIZE;

	for (i = 0; i < LIGHTMAP_SIZE - w; i++)
	{
		best2 = 0;

		for (j = 0; j < w; j++)
		{
			if (lm_allocated[i + j] >= best)
				break;

			if (lm_allocated[i + j] > best2)
				best2 = lm_allocated[i + j];
		}

		if (j == w)
		{
			// this is a valid spot
			*x = i;
			*y = best = best2;
		}
	}

	if (best + h > LIGHTMAP_SIZE)
		return FALSE;

	for (i = 0; i < w; i++)
		lm_allocated[*x + i] = best + h;

	return TRUE;
}


/*
========================
GL_CreateSurfaceLightmap
========================
*/
void GL_CreateSurfaceLightmap (msurface_t *surf)
{
	if (surf->flags & (SURF_DRAWSKY | SURF_DRAWTURB)) return;

	int smax = (surf->extents[0] >> 4) + 1;
	int tmax = (surf->extents[1] >> 4) + 1;

	if (!LM_AllocBlock (smax, tmax, &surf->light_s, &surf->light_t))
	{
		memset (lm_allocated, 0, sizeof (lm_allocated));

		if ((++current_lightmap_texture) >= MAX_LIGHTMAPS) Sys_Error ("MAX_LIGHTMAPS exceeded");
		if (!LM_AllocBlock (smax, tmax, &surf->light_s, &surf->light_t)) Sys_Error ("Successive calls to LM_AllocBlock failed");
	}

	// assign the lightmap texture
	surf->lightmaptexturenum = current_lightmap_texture;
	byte *lightmap = surf->samples;

	// no styles on this surface yet
	surf->NumStyles = 0;

	// no lightmap on this surface
	if (!lightmap)
	{
		// the surface must have at least 1 style
		R_ActivateLightStyle (0, surf->lightmaptexturenum);
		surf->NumStyles = 1;
		return;
	}

	// and now build up the styles
	for (int style = 0; style < MAX_SURFACESTYLES && surf->styles[style] != 255; style++)
	{
		// ensure that the style is active
		R_ActivateLightStyle (style, surf->lightmaptexturenum);

		// get our stuff for it
		unsigned *dest = &d3d_LightmapData[style][surf->lightmaptexturenum][surf->light_t * LIGHTMAP_SIZE + surf->light_s];

		for (int i = 0; i < tmax; i++)
		{
			// lit files were renormalized back to BSP scale with the alpha channel used for an overbrighting boost if needed,
			// so here we need to read 4-component lighting
			for (int j = 0; j < smax; j++, lightmap += 4)
				dest[j] = (lightmap[3] << 24) | (lightmap[2] << 16) | (lightmap[1] << 8) | lightmap[0];

			dest += LIGHTMAP_SIZE;
		}

		// go to the next style
		surf->NumStyles++;
	}
}


void GL_BeginBuildingLightmaps (void)
{
	// begin the allocation sequence
	memset (lm_allocated, 0, sizeof (lm_allocated));
	current_lightmap_texture = 0;

	// clear the light data
	memset (d3d_LightmapData, 0, sizeof (d3d_LightmapData));

	// and the active styles
	memset (ActiveStyles, 0, sizeof (ActiveStyles));
}


void GL_EndBuildingLightmaps (void)
{
	// increment the current number so that it can be used for loops/etc
	current_lightmap_texture++;

	// upload any lightmaps that were filled
	for (int style = 0; style < MAX_SURFACESTYLES; style++)
	{
		// clear the existing texture
		SAFE_RELEASE (d3d_LightmapSRVs[style]);

		// if this style wasn't used on any surfaces then don't bother
		if (!ActiveStyles[style]) continue;

		// set up initial data for the texture
		D3D10_SUBRESOURCE_DATA srd[MAX_LIGHTMAPS];

		// add the lightmaps to the initial data
		for (int lm = 0; lm < current_lightmap_texture; lm++)
		{
			// a style may have been activated but for a different lightmap; this ensures that it has a valid data pointer
			R_ActivateLightStyle (style, lm);

			// clear to fullbright if there's no light data in the world
			if (!cl.worldmodel->lightdata)
				memset (d3d_LightmapData[style][lm], 0xffffffff, LIGHTMAP_SIZE * LIGHTMAP_SIZE * 4);

			srd[lm].pSysMem = d3d_LightmapData[style][lm];
			srd[lm].SysMemPitch = LIGHTMAP_SIZE * 4;
			srd[lm].SysMemSlicePitch = 0;

			// any further attempt to access this memory is an error
			d3d_LightmapData[style][lm] = NULL;
		}

		// setup to create a texture
		ID3D10Texture2D *Texture = NULL;
		D3D10_TEXTURE2D_DESC desc;

		// creating it as the appropriately sized texture array
		desc.ArraySize = current_lightmap_texture;
		desc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
		desc.CPUAccessFlags = 0;
		desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		desc.Height = LIGHTMAP_SIZE;
		desc.MipLevels = 1;
		desc.MiscFlags = 0;
		desc.SampleDesc.Count = 1;
		desc.SampleDesc.Quality = 0;
		desc.Usage = D3D10_USAGE_IMMUTABLE;
		desc.Width = LIGHTMAP_SIZE;

		// now create the texture
		if (FAILED (d3d_Device->CreateTexture2D (&desc, srd, &Texture)))
			Sys_Error ("GL_EndBuildingLightmaps : CreateTexture2D failed");

		// now create a shader resource view on it
		if (FAILED (d3d_Device->CreateShaderResourceView (Texture, NULL, &d3d_LightmapSRVs[style])))
			Sys_Error ("GL_EndBuildingLightmaps : CreateShaderResourceView failed");

		// this works because CreateShaderResourceView will hold a reference to the texture object
		// doing this ensures that the texture object can not be touched outside of here (which is what we want)
		Texture->Release ();
	}

	// bind the lightmap textures from slots 10 onwards so that they won't be overwritten and need only be bound once per map
	// (this also needs doing if we chuck the state on a mode change so we just call the mode change handler here...)
	// this also re-initializes lightstyles at their default values and binds the cbuffer for lightstyles
	d3d_LightHandler.OnModeChange ();
}


/*
==================
R_AnimateLight
==================
*/
void R_AnimateLight (double time)
{
	// 'm' is normal light, 'a' is no light, 'z' is double bright
	int flight = (int) floor (time * 10.0f);
	int clight = (int) ceil (time * 10.0f);
	float lerpfrac = (time * 10.0f) - flight;
	BOOL modified = FALSE;

	for (int i = 0; i < MAX_LIGHTSTYLES; i++)
	{
		float light = 'm';

		if (!r_dynamic->value)
			light = 'm';
		else if (!cl_lightstyle[i].length)
			light = 'm';
		else if (cl_lightstyle[i].length == 1)
			light = cl_lightstyle[i].map[0];
		else
		{
			light = (
				(float) (cl_lightstyle[i].map[flight % cl_lightstyle[i].length]) * (1.0f - lerpfrac) + 
				(float) (cl_lightstyle[i].map[clight % cl_lightstyle[i].length]) * lerpfrac
			);
		}

		// adiust for final range
		light = ((light - 'a') / 6.0f) * r_lightscale->value;

		// store out and flag as modified
		if (light != d_lightstylevalue[i])
		{
			d_lightstylevalue[i] = light;
			modified = TRUE;
		}
	}

	// if we're using realtime lighting we don't need to write out the styles to our shader
	if (!r_realtime_lighting->boolean && modified)
	{
		float *c = NULL;

		if (SUCCEEDED (d3d_LightConstants->Map (D3D10_MAP_WRITE_DISCARD, 0, (void **) &c)))
		{
			memcpy (c, d_lightstylevalue, sizeof (d_lightstylevalue));
			d3d_LightConstants->Unmap ();
		}
	}
}


/*
=============================================================================

LIGHT SAMPLING

=============================================================================
*/

BOOL R_RecursiveLightPoint (mnode_t *node, vec3_t start, vec3_t end, lightpoint_t *lightpoint)
{
	vec3_t	mid;

	if (node->contents < 0)
		return -1;		// didn't hit anything

	// calculate mid point
	// FIXME: optimize for axial
	mplane_t *plane = node->plane;
	float front = Vector3DotH (start, plane->normal, -plane->dist);
	float back = Vector3DotH (end, plane->normal, -plane->dist);
	int side = front < 0 ? 1 : 0;

	if ((back < 0) == side)
		return R_RecursiveLightPoint (node->children[side], start, end, lightpoint);

	Mod_GetBBoxCenter (mid, start, end, front / (front - back));

	// go down front side
	if (R_RecursiveLightPoint (node->children[side], start, mid, lightpoint) >= 0) return TRUE;		// hit something
	if ((back < 0) == side) return FALSE;		// didn't hit anuthing

	// check for impact on this node
	msurface_t *surf = cl.worldmodel->surfaces + node->firstsurface;

	for (int i = 0; i < node->numsurfaces; i++, surf++)
	{
		if (surf->flags & SURF_DRAWSKY) continue;
		if (surf->flags & SURF_DRAWTURB) continue;

		mtexinfo_t *tex = surf->texinfo;

		int s = Vector3DotH (mid, tex->vecs[0], tex->vecs[0][3]);
		int t = Vector3DotH (mid, tex->vecs[1], tex->vecs[1][3]);

		if (s < surf->texturemins[0] || t < surf->texturemins[1])
			continue;

		int ds = s - surf->texturemins[0];
		int dt = t - surf->texturemins[1];

		if (ds > surf->extents[0] || dt > surf->extents[1])
			continue;

		if (!surf->samples)
			return FALSE;

		ds >>= 4;
		dt >>= 4;

		byte *lightmap = surf->samples;

		if (lightmap)
		{
			// lit files were renormalized back to BSP scale with the alpha channel used for an overbrighting boost if needed,
			// so here we need to read 4-component lighting
			lightmap += (dt * ((surf->extents[0] >> 4) + 1) + ds) * 4;

			for (int maps = 0; maps < MAX_SURFACESTYLES && surf->styles[maps] != 255; maps++)
			{
				// bring scale up to range for mdl lighting
				float scale = d_lightstylevalue[surf->styles[maps]];

				// store it out for later use
				lightpoint->lightmap[maps][0] = lightmap[0];
				lightpoint->lightmap[maps][1] = lightmap[1];
				lightpoint->lightmap[maps][2] = lightmap[2];
				lightpoint->lightstyles[maps] = surf->styles[maps];

				// lit files were renormalized back to BSP scale with the alpha channel used for an overbrighting boost if needed,
				// so here we need to read 4-component lighting
				lightmap += (((surf->extents[0] >> 4) + 1) * ((surf->extents[1] >> 4) + 1)) * 4;
			}
		}

		return TRUE;
	}

	// go down back side
	return R_RecursiveLightPoint (node->children[!side], mid, end, lightpoint);
}


void R_ClearLightPoint (lightpoint_t *lightpoint)
{
	for (int i = 0; i < MAX_SURFACESTYLES; i++)
	{
		lightpoint->lightmap[i][0] = 0;
		lightpoint->lightmap[i][1] = 0;
		lightpoint->lightmap[i][2] = 0;
		lightpoint->lightstyles[i] = 255;
	}
}


void R_LightPointToShadeLight (lightpoint_t *lightpoint, float *shadelight)
{
	// begin with no light
	Vector3Set (shadelight, 0, 0, 0);

	for (int maps = 0; maps < MAX_SURFACESTYLES && lightpoint->lightstyles[maps] != 255; maps++)
	{
		// bring scale up to range for mdl lighting
		float scale = d_lightstylevalue[lightpoint->lightstyles[maps]];

		// accumulate to colour
		shadelight[0] += (float) lightpoint->lightmap[maps][0] * scale;
		shadelight[1] += (float) lightpoint->lightmap[maps][1] * scale;
		shadelight[2] += (float) lightpoint->lightmap[maps][2] * scale;
	}

	// same scaling as is used for BSP lighting
	VectorScale (shadelight, (1.0f / 255.0f), shadelight);
}


void R_LightPoint (vec3_t p, lightpoint_t *lightpoint)
{
	// ensure that it always hits something
	float end[3] = {p[0], p[1], cl.worldmodel->mins[2] - 10.0f};

	R_ClearLightPoint (lightpoint);

	// and see what it hits
	R_RecursiveLightPoint (cl.worldmodel->nodes, p, end, lightpoint);
}


void R_RecursiveCullLight (dlight_t *dl, mnode_t *node)
{
	// already done
	if (dl->visframe == ref.FrameCount) return;

	// not in PVS
	if (node->visframe != ref.VisFrameCount) return;

	// node is solid or a leaf
	if (node->contents == CONTENTS_SOLID) return;

	if (node->contents < 0)
	{
		// we've found a light in the client PVS so mark it and get out
		dl->visframe = ref.FrameCount;
		return;
	}

	// find which side of the node we are on
	mplane_t *plane = node->plane;
	float dot = Vector3Dot (dl->origin, plane->normal) - plane->dist;
	int side = (dot >= 0) ? 0 : 1;

	// stay on target
	if (dot > dl->radius && side == 1) return;
	if (dot < -dl->radius && side == 0) return;

	// recurse down the children, front side first
	if (dl->visframe != ref.FrameCount) R_RecursiveCullLight (dl, node->children[side]);

	// stay on target
	if (dot > dl->radius && side == 0) return;
	if (dot < -dl->radius && side == 1) return;

	// recurse down the back side
	if (dl->visframe != ref.FrameCount) R_RecursiveCullLight (dl, node->children[!side]);
}


BOOL R_CullLight (dlight_t *dl)
{
	// check for lights with no contribution
	if (!(d_lightstylevalue[dl->style] > 0)) return TRUE;
	if (!dl->diffusescale) return TRUE;

	// the frustum cull check was moved from here to the individual gather loops because we don't want to release
	// the shadow buffer for a light that was only frustum culled

	// check for light in the client PVS
	mleaf_t *leaf = Mod_PointInLeaf (dl->origin, cl.worldmodel);

	// fast accept case - if the leaf containing the light is in the client PVS then the light is definitely in the client PVS
	if (leaf->visframe == ref.VisFrameCount) return FALSE;

	// fast reject case - don't add lights in solid
	// we can't do this because grenades are embedded in the floor so they falsely register as solid
	//if (leaf->contents == CONTENTS_SOLID) return TRUE;

	// do a full cull
	R_RecursiveCullLight (dl, cl.worldmodel->nodes);

	// return result
	return !(dl->visframe == ref.FrameCount);
}


void R_GatherLights (void)
{
	// starting with no lights
	r_numactivelights = 0;

	// no dlights to be added
	if (!r_dynamic->value) return;
	if (!cl.worldmodel->lightdata) return;
	if (r_fullbright->value) return;

	// add all realtime lights
	if (r_realtime_lighting->boolean)
	{
		for (int i = 0; i < r_num_realtimelights; i++)
		{
			dlight_t *dl = &r_realtimelights[i];

			// perform a simple frustum cull first
			if (R_CullSphere (dl->origin, dl->radius, 15)) continue;

			// set the correct style
			dl->stylevalue = d_lightstylevalue[dl->style];

			// never associated with an entity
			dl->key = -1;

			// this is now a valid light so add it to the list
			if (!R_CullLight (dl) && r_numactivelights < MAX_ACTIVE_LIGHTS)
			{
				r_activelights[r_numactivelights] = dl;
				r_numactivelights++;
			}
		}
	}

	// add all dynamic lights
	for (int i = 0; i < MAX_DLIGHTS; i++)
	{
		dlight_t *dl = &cl_dlights[i];

		// this is not a valid dlight
		if (!dl->Alive ()) continue;

		// perform a simple frustum cull first
		if (R_CullSphere (dl->origin, dl->radius, 15)) continue;

		// unstyled and never a spotlight
		dl->stylevalue = 2.0f;
		dl->falloff = 65536;

		// dynamic lights should always cast shadows
		if (r_realtime_lighting->boolean)
			dl->Shadow = TRUE;
		else dl->Shadow = FALSE;

		// this is now a valid light so add it to the list
		if (!R_CullLight (dl) && r_numactivelights < MAX_ACTIVE_LIGHTS)
		{
			r_activelights[r_numactivelights] = dl;
			r_numactivelights++;
		}
	}
}


